<template>
  <div ref="root" class="h-full">
    <!-- 视频窗口展示 -->
    <div v-if="loadSuccess" :id="id" class="h-full" ref="videoWrapper"></div>

    <!-- 加载失败窗口 -->
    <div v-else class="hik-video-replay__loading">
      <div class="hik-video-replay__loading--fail">
        <el-result
          icon="error"
          title="控件未安装或加载失败"
          subTitle="若未安装成功，请下载安装控件；若已安装控件，请先尝试重启浏览器重试当前操作；若问题仍然存在，建议检查网络或重新安装控件。"
        >
          <template v-slot:extra>
            <a
              href="https://open.hikvision.com/fileserver/filesonline/%E8%A7%86%E9%A2%91WEB%E6%8F%92%E4%BB%B6_Win32%20V1.4.2_20200827190545.rar"
            >
              <el-button type="primary" size="medium">下载控件</el-button>
            </a>
          </template>
        </el-result>
      </div>
    </div>
  </div>
</template>

<script>
import { Loading } from "element-ui";
import { v4 as uuidv4 } from "uuid";
import { WebControl } from "@/lib/hikvision-web-plugin/jsWebControl-1.0.0.min";
import $ from "jquery";
import log from "@/lib/app-logger/index";

let thisVue;
export default {
  // 全局感知
  name: "HikvisionVideo",
  // 模板依赖
  components: {},
  // 接口
  props: {
    appKey: {
      type: String,
    },
    secret: {
      type: String,
    },
    ip: {
      type: String,
    },
    replay: {
      type: Boolean,
      default: true,
    },
    /**
     * {
     *   code,
     *   startTimeStamp,
     *   endTimeStamp
     * }
     * */
    cameraList: {
      type: Array,
    },
  },
  // 本地状态
  data() {
    return {
      id: uuidv4(),

      loadingInstance: {},

      // 插件实例
      oWebControl: {},

      // 启动插件重试次数，超过2次则判断启动失败
      initCount: 0,

      loadSuccess: true,

      // 公钥
      pubKey: "",
    };
  },
  computed: {},
  // 监听事件
  watch: {},
  // 生命周期钩子函数
  created() {
    thisVue = this;
  },
  mounted() {
    this.initPlugin();
  },
  beforeDestroy() {
    this.closeVideo();
  },
  // 非响应式的property
  methods: {
    initPlugin() {
      this.showLoading();
      /*
       * 1. 要使用视频 WEB 插件，首先需要创建WebControl 实例。
       */
      this.oWebControl = new WebControl({
        //指定容器id
        szPluginContainer: thisVue.id,
        //指定起止端口号，建议使用该值
        iServicePortStart: 15900,
        iServicePortEnd: 15909,
        // 创建成功回调
        cbConnectSuccess: thisVue.cbConnectSuccess,
        // 创建失败回调
        cbConnectError: thisVue.cbConnectError,
        //  插件使用过程中发生的断开与插件服务连接的回调
        cbConnectClose: thisVue.cbConnectClose,
      });
    },
    // 开启加载动画
    showLoading() {
      let loadingInstance = Loading.service({
        target: thisVue.$refs.root,
        text: "插件启动中，请稍候...",
      });
      this.loadingInstance = loadingInstance;
    },
    // 关闭加载动画
    hideLoading(loadSuccess) {
      this.loadSuccess = loadSuccess;
      this.loadingInstance.close();
    },
    // 插件创建失败回调
    cbConnectError() {
      log.debug("cbConnectError");
      this.oWebControl = null;

      // 程序未启动时执行error函数，采用wakeup来启动程序。
      // 插件未启动，正在尝试启动，请稍候...
      WebControl.JS_WakeUp("VideoWebPlugin://");
      thisVue.initCount++;
      this.$nextTick(() => {
        if (thisVue.initCount < 3) {
          setTimeout(function () {
            thisVue.initPlugin();
          }, 3000);
          return;
        }

        log.debug("插件启动失败");
        this.hideLoading(false);
      });
    },
    // 插件使用过程中发生的断开与插件服务连接的回调
    cbConnectClose(bNormalClose) {
      // bNormalClose = false时表示异常断开
      // bNormalClose = true时表示正常断开
      log.debug("cbConnectClose");
      this.hideLoading(false);
      this.oWebControl = null;
    },
    // 插件创建成功回调
    cbConnectSuccess() {
      /* 2. 创建 WebControl 实例后即可在其创建成功回调中启动服务，在启动服务成功回调中设置消息回调并创建插件窗口。
          在创建 WebControl 实例失败回调中可尝试唤醒 WebControl.exe ，也可以再次尝试创建 WebControl 实例。
          */
      // 实例创建成功后需要启动服务
      log.debug("cbConnectSuccess");
      this.oWebControl
        .JS_StartService("window", {
          dllPath: "./VideoPluginConnect.dll",
        })
        .then(thisVue.serviceStartSuccess, thisVue.serviceStartFail)
        .catch((e) => {
          this.serviceStartFail();
        });
    },
    serviceStartFail() {
      log.debug("启动插件服务失败");
      this.hideLoading(false);
    },

    // 插件服务启动成功回调
    serviceStartSuccess() {
      // 启动服务成功回调
      log.debug("serviceStartSuccess");
      // 设置消息回调
      this.oWebControl.JS_SetWindowControlCallback({
        cbIntegrationCallBack: this.cbIntegrationCallBack,
      });

      let domRect = this.$refs.videoWrapper.getBoundingClientRect();

      // JS_CreateWnd创建视频播放窗口，宽高可设定
      this.oWebControl
        .JS_CreateWnd(this.id, domRect.width, domRect.height)
        .then(this.createWndSuccess, this.createWndFailure)
        .catch(() => {
          this.createWndFailure();
        });
    },

    // 插件事件回调
    cbIntegrationCallBack(oData) {
      console.log("插件回调消息", oData);
      let type = oData.responseMsg.type;
      if (type !== 2) {
        return;
      }
      if (oData.responseMsg.msg.result !== 816) {
        return;
      }

      this.$emit("video-close", oData.responseMsg.msg.wndId - 1);
    },

    createWndFailure() {
      log.debug("创建视频播放窗口失败");
      this.hideLoading(false);
    },
    createWndSuccess() {
      log.debug("创建视频播放窗口成功，开始初始化");

      // 获取公钥
      this.getPubKey();
      this.hideLoading(true);
      this.listenEvent();
    },
    getPubKey() {
      this.oWebControl
        .JS_RequestInterface({
          funcName: "getRSAPubKey",
          argument: JSON.stringify({
            keyLength: 1024,
          }),
        })
        .then((oData) => {
          this.getPubKeySuccess(oData);
        })
        .catch(() => {
          this.getPubKeyFailure();
        });
    },
    getPubKeySuccess(oData) {
      log.debug("获取公钥成功：{}", oData);
      if (oData.responseMsg.data) {
        this.pubKey = oData.responseMsg.data;
        // 设置回放的相关信息
        this.setPlayInfo();
      }
    },
    getPubKeyFailure() {
      log.debug("获取公钥失败");
      this.hideLoading(false);
    },
    setPlayInfo() {
      ////////////////////////////////// 请自行修改以下变量值	////////////////////////////////////
      // 综合安防管理平台提供的appkey，必填
      let appkey = this.appKey;
      // 综合安防管理平台提供的secret，必填
      let secret = this.setEncrypt(this.secret);
      // 综合安防管理平台IP地址，必填
      let ip = this.ip;
      // 初始播放模式：0-预览，1-回放
      let playMode = this.replay ? 1 : 0;
      // 综合安防管理平台端口，若启用HTTPS协议，默认443
      let port = 443;
      // 抓图存储路径
      let snapDir = "D:\\SnapDir";
      // 紧急录像或录像剪辑存储路径
      let videoDir = "D:\\VideoDir";
      // playMode指定模式的布局
      let layout = "1x1";
      //是否启用HTTPS协议与综合安防管理平台交互，这里总是填1
      let enableHTTPS = 1;
      // 加密字段，默认加密领域为secret
      let encryptedFields = "secret";
      // 是否显示工具栏，0-不显示，非0-显示
      let showToolbar = 1;
      // 是否显示智能信息（如配置移动侦测后画面上的线框），0-不显示，非0-显示
      let showSmart = 1;
      // 自定义工具条按钮
      let buttonIDs = "0,16,256,257,258,259,260,512,513,515,516,768,769";
      // 重连次数，回放异常情况下有效
      //let reconnectTimes = 2;
      // 每次重连的重连间隔 >= reconnectTime
      //let reconnectTime = 4;
      ////////////////////////////////// 请自行修改以上变量值	////////////////////////////////////
      this.oWebControl
        .JS_RequestInterface({
          funcName: "init",
          argument: JSON.stringify({
            // API网关提供的appkey
            appkey: appkey,
            // API网关提供的secret
            secret: secret,
            // API网关IP地址
            ip: ip,
            // 播放模式（决定显示预览还是回放界面）
            playMode: playMode,
            // 端口
            port: port,
            // 抓图存储路径
            snapDir: snapDir,
            // 紧急录像或录像剪辑存储路径
            videoDir: videoDir,
            //  布局
            layout: layout,
            // 是否启用HTTPS协议
            enableHTTPS: enableHTTPS,
            // 加密字段
            encryptedFields: encryptedFields,
            // 是否显示工具栏
            showToolbar: showToolbar,
            // 是否显示智能信息
            showSmart: showSmart,
            // 自定义工具条按钮
            buttonIDs: buttonIDs,
            // 重连次数
            //reconnectTimes：reconnectTimes,
            // 重连间隔
            //reconnectDuration：reconnectTime
          }),
        })
        .then((oData) => {
          this.requestInterfaceSuccess(oData);
        })
        .catch(() => {
          this.requestInterfaceFailure();
        });
    },
    // 公钥编码
    setEncrypt(value) {
      let encrypt = new JSEncrypt();
      encrypt.setPublicKey(this.pubKey);
      return encrypt.encrypt(value);
    },
    requestInterfaceFailure() {
      log.debug("requestInterfaceFailure");
      this.hideLoading(false);
    },
    requestInterfaceSuccess(oData) {
      log.debug("requestInterfaceSuccess: {}", oData);
      this.resizeVideo();
      this.hideLoading(true);
      this.startPlay();
    },
    resizeVideo() {
      let domRect = this.$refs.videoWrapper.getBoundingClientRect();
      this.oWebControl.JS_Resize(domRect.width, domRect.height);
    },
    // 开始回放
    startPlay() {
      this.cameraList.forEach((cameraItem) => {
        this.playVideo(cameraItem);
      });
    },
    playVideo(cameraItem, wndId = 0) {
      let argument = {
        //获取输入的监控点编号值，必填
        cameraIndexCode: cameraItem.code,
        //是否启用GPU硬解，0-不启用，1-启用
        gpuMode: 0,
        //传输协议：0-UDP，1-TCP
        transMode: 1,
        //播放窗口序号（在2x2以上布局下可指定播放窗口）
        wndId: wndId,
      };
      let funcName = "startPlayback";

      if (!this.replay) {
        // 主子码流标识：0-主码流，1-子码流
        argument.streamMode = 0;
        funcName = "startPreview";
      } else {
        // 回放开始时间戳，必填
        let startTimeStamp = cameraItem.startTimeStamp;
        // 回放结束时间戳，必填
        let endTimeStamp = cameraItem.endTimeStamp;

        // 回放开始时间戳，单位秒。必填
        argument.startTimeStamp = Math.floor(startTimeStamp / 1000).toString();
        // 回放结束时间戳，单位秒，必填
        argument.endTimeStamp = Math.floor(endTimeStamp / 1000).toString();
        // 录像存储类型：0-中心存储，1-设备存储
        argument.recordLocation = 0;
      }
      this.oWebControl.JS_RequestInterface({
        funcName: funcName,
        argument: JSON.stringify(argument),
      });
      log.debug("funcName: {}", funcName);
      log.debug("argument: {}", argument);
    },
    // 停止播放
    stopAllPlay() {
      let funcName = "stopAllPlayback";
      if (!this.replay) {
        funcName = "stopAllPreview";
      }
      this.oWebControl.JS_RequestInterface({
        funcName,
      });
    },
    // 设置超出浏览器的部分插件窗口隐藏或显示
    // 设置窗口裁剪，当因滚动条滚动导致窗口需要被遮住的情况下需要JS_CuttingPartWindow部分窗口
    setWndCover() {
      let videoWrapperEl = this.$refs.videoWrapper;

      // 矩形
      let domRect = videoWrapperEl.getBoundingClientRect();
      // 容器宽度
      let videoWidth = domRect.width;
      let videoHeight = domRect.height;

      // 矩形的top、left、right、bottom都以矩形的上边和左边为基准，分正负，是到window的距离
      // 矩形的 width 与 height 为容器的宽和高

      // 需要裁切的部分
      let coverLeft = domRect.left < 0 ? Math.abs(domRect.left) : 0;
      let coverTop = domRect.top < 0 ? Math.abs(domRect.top) : 0;
      let coverRight =
        domRect.right - videoWidth > 0
          ? Math.round(domRect.right - videoWidth)
          : 0;
      let coverBottom =
        domRect.bottom - videoHeight > 0
          ? Math.round(domRect.bottom - videoHeight)
          : 0;

      coverLeft = coverLeft > videoWidth ? videoWidth : coverLeft;
      coverTop = coverTop > videoHeight ? videoHeight : coverTop;
      coverRight = coverRight > videoWidth ? videoWidth : coverRight;
      coverBottom = coverBottom > videoHeight ? videoHeight : coverBottom;

      // 多1个像素点防止还原后边界缺失一个像素条
      this.oWebControl.JS_RepairPartWindow(0, 0, videoWidth + 1, videoHeight);
      if (coverLeft !== 0) {
        this.oWebControl.JS_CuttingPartWindow(0, 0, coverLeft, videoHeight);
      }
      if (coverTop !== 0) {
        // 多剪掉一个像素条，防止出现剪掉一部分窗口后出现一个像素条
        this.oWebControl.JS_CuttingPartWindow(0, 0, videoWidth + 1, coverTop);
      }
      if (coverRight !== 0) {
        this.oWebControl.JS_CuttingPartWindow(
          videoWidth - coverRight,
          0,
          coverRight,
          videoHeight
        );
      }
      if (coverBottom !== 0) {
        this.oWebControl.JS_CuttingPartWindow(
          0,
          videoHeight - coverBottom,
          videoWidth,
          coverBottom
        );
      }
    },
    listenEvent() {
      // 监听resize事件，使插件窗口尺寸跟随DIV窗口变化
      $(window).resize(function () {
        requestAnimationFrame(() => {
          if (thisVue.oWebControl != null) {
            thisVue.resizeVideo();
            thisVue.setWndCover();
          }
        });
      });

      // 监听滚动条scroll事件，使插件窗口跟随dialog滚动而移动
      $(".el-dialog__wrapper").scroll(function () {
        requestAnimationFrame(() => {
          if (thisVue.oWebControl != null) {
            thisVue.resizeVideo();
            thisVue.setWndCover();
          }
        });
      });
    },
    closeVideo() {
      if (this.oWebControl && this.oWebControl !== {}) {
        /*
             IE 浏览器中使用插件存在插件窗口销毁耗时导致浏览器页面消息后可能还会显示一会，
             可在调 JS _Disconnect 前调 JS_HideWnd 接口先将插件窗口隐藏。
          */
        this.oWebControl.JS_HideWnd();
        this.oWebControl
          .JS_DestroyWnd()
          .then(() => {
            log.debug("摧毁窗口成功");
          })
          .catch(() => {
            log.debug("摧毁窗口失败");
          });
        this.oWebControl
          .JS_Disconnect()
          .then(() => {
            log.debug("断开插件连接成功");
          })
          .catch(() => {
            log.debug("断开插件连接失败");
          });
      }
    },
    setPluginLayout(layout) {
      // 未指定或者指定无效，则为2x2
      this.oWebControl.JS_RequestInterface({
        funcName: "setLayout",
        argument: `{ layout: ${layout}}`,
      });
    },
  },
};
</script>

<style scoped lang="less">
.playWnd {
  height: 100%;
}

.hik-video-replay__loading {
  text-align: center;
  min-height: 300px;
  background-color: white;
  height: 100%;
}

.hik-video-replay__loading--fail {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;

  .el-result {
    padding-top: 20px;
    padding-bottom: 20px;
  }
}

.loading-fail__title {
  font-size: 24px;
  color: #5a5a65;
  padding-bottom: 8px;
}
</style>
